package com.aloda.ojjudge.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.aloda.api.client.QuestionClient;
import com.aloda.api.domain.Question;
import com.aloda.api.domain.QuestionSubmit;
import com.aloda.ojcommon.constant.ExceptionConstant;
import com.aloda.ojcommon.domain.entity.JudgeCase;
import com.aloda.ojcommon.domain.entity.JudgeConfig;
import com.aloda.ojcommon.domain.entity.JudgeInfo;
import com.aloda.ojcommon.domain.enums.JudgeInfoMessageEnum;
import com.aloda.ojcommon.domain.enums.QuestionSubmitStatusEnum;
import com.aloda.ojcommon.exception.BaseException;
import com.aloda.ojcommon.exception.ParamsException;
import com.aloda.ojjudge.codesandbox.CodeSandbox;
import com.aloda.ojjudge.codesandbox.CodeSandboxFactory;
import com.aloda.ojjudge.codesandbox.CodeSandboxProxy;
import com.aloda.ojjudge.judge.JudgeStrategy;
import com.aloda.ojjudge.pojo.dto.ExecuteCodeRequest;
import com.aloda.ojjudge.pojo.dto.ExecuteCodeResponse;
import com.aloda.ojjudge.pojo.dto.JudgeContext;
import com.aloda.ojjudge.pojo.enums.ResponseEnum;
import com.aloda.ojjudge.properties.CodeSandboxProperties;
import com.aloda.ojjudge.service.JudgeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 判题服务层实现类
 *
 * @Author aloda
 * @Date 2025/4/29 14:01
 */
@Slf4j
@Service
public class JudgeServiceImpl implements JudgeService {

    @Resource
    private QuestionClient questionClient;

    @Resource
    private CodeSandboxProperties codeSandboxProperties;

    @Resource
    private JudgeStrategy judgeStrategy;

    /**
     * 根据提交记录id做判题任务
     *
     * @param id 提交记录id
     */
    @Override
    public void doJudge(Long id) {
        // 1.根据id查询提交记录、对应题目信息
        QuestionSubmit questionSubmit = questionClient.getQuestionSubmitById(id);
        if (BeanUtil.isEmpty(questionSubmit)) {
            throw new ParamsException(ExceptionConstant.QUESTION_SUBMIT_IS_NOT_EXIST);
        }
        Question question = questionClient.getById(questionSubmit.getQuestionId());
        if (BeanUtil.isEmpty(question)) {
            throw new ParamsException(ExceptionConstant.QUESTION_IS_NOT_EXIST);
        }

        // 2.判断是否为等待判题的题目，如果不是返回错误
        if (!questionSubmit.getSubmitStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())) {
            throw new ParamsException(ExceptionConstant.QUESTION_SUBMIT_IS_NOT_WAITING);
        }

        // 3.将题目状态修改为判题中，避免重复判题
        QuestionSubmit questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmit.getId());
        questionSubmitUpdate.setSubmitStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        boolean update = questionClient.updateQuestionSubmit(questionSubmitUpdate);
        if (!update) {
            throw new BaseException(ExceptionConstant.QUESTION_SUBMIT_STATUS_UPDATE_ERROR);
        }

        // 4.调用沙箱，获取到执行结果
        CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(codeSandboxProperties.getCodeSandboxType());
        codeSandbox = new CodeSandboxProxy(codeSandbox);

        // 获取输入用例
        List<JudgeCase> judgeCaseList = JSONUtil.toList(question.getJudgeCase(), JudgeCase.class);
        List<String> questionInputList = judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());

        JudgeConfig judgeConfig = JSONUtil.toBean(question.getJudgeConfig(), JudgeConfig.class);
        // 构建请求
        ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
                .code(questionSubmit.getSubmitCode())
                .language(questionSubmit.getSubmitLanguage())
                .inputList(questionInputList)
                .time(judgeConfig.getTimeLimit())
                .memory(judgeConfig.getMemoryLimit())
                .build();

        ExecuteCodeResponse executeCodeResponse = null;
        try {
            executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
            if (executeCodeResponse == null) {
                throw new BaseException(ExceptionConstant.CODE_SANDBOX_ERROR);
            }
        } catch (RuntimeException e) {
            this.sendUpdateQuestionSubmitMessage(questionSubmit.getId(), null, QuestionSubmitStatusEnum.FAILED);
            throw new BaseException(ExceptionConstant.CODE_SANDBOX_ERROR);
        }
        // 如果调用成功但是返回值不是成功，直接设置判题状态
        if (!executeCodeResponse.getStatus().equals(ResponseEnum.SUCCESS.getStatus())) {
            JudgeInfo judgeInfo = new JudgeInfo();
            judgeInfo.setMessage(executeCodeResponse.getMessage());
            judgeInfo.setMemory(0L);
            judgeInfo.setTime(0L);
            this.sendUpdateQuestionSubmitMessage(questionSubmit.getId(), judgeInfo, QuestionSubmitStatusEnum.SUCCEED);
            throw new BaseException(ExceptionConstant.CODE_SANDBOX_ERROR);
        }

        // 5.根据沙箱的执行结果，设置题目的判题状态和信息
        JudgeContext judgeContext = JudgeContext.builder()
                .judgeInfo(executeCodeResponse.getJudgeInfo())
                .inputList(questionInputList)
                .outputList(executeCodeResponse.getOutputList())
                .judgeCaseList(judgeCaseList)
                .question(question)
                .questionSubmit(questionSubmit)
                .build();
        JudgeInfo judgeInfo = null;
        try {
            judgeInfo = judgeStrategy.doJudge(judgeContext);
        } catch (Exception e) {
            judgeInfo = new JudgeInfo();
            judgeInfo.setMessage(ExceptionConstant.JUDGE_ERROR);
            judgeInfo.setMemory(executeCodeResponse.getJudgeInfo().getMemory());
            judgeInfo.setTime(executeCodeResponse.getJudgeInfo().getTime());
            this.sendUpdateQuestionSubmitMessage(questionSubmit.getId(), judgeInfo, QuestionSubmitStatusEnum.FAILED);
            throw new BaseException(ExceptionConstant.JUDGE_ERROR);
        }

        // 6.修改数据库中的判题结果
        this.sendUpdateQuestionSubmitMessage(questionSubmit.getId(), judgeInfo, QuestionSubmitStatusEnum.SUCCEED);

        // 7.修改题目通过数量
        if (Objects.equals(JudgeInfoMessageEnum.getEnumByValue(judgeInfo.getMessage()), JudgeInfoMessageEnum.ACCEPTED)) {
            questionClient.addAcceptedNum(question.getId());
        }
    }

    /**
     * 发送题目提交记录更新消息
     *
     * @param questionSubmitId
     * @param judgeInfo
     * @param questionSubmitStatusEnum
     */
    private void sendUpdateQuestionSubmitMessage(Long questionSubmitId, JudgeInfo judgeInfo, QuestionSubmitStatusEnum questionSubmitStatusEnum) {
        QuestionSubmit questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmitId);
        questionSubmitUpdate.setSubmitStatus(questionSubmitStatusEnum.getValue());
        questionSubmitUpdate.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        questionClient.updateQuestionSubmit(questionSubmitUpdate);
    }
}
